<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="Q0tkg" id="Q0tkg"><span data-lake-id="u86c238a0" id="u86c238a0">典型回答</span></h1>
  <p data-lake-id="u6eb6ba53" id="u6eb6ba53"><br></p>
  <p data-lake-id="u83b687db" id="u83b687db"><br></p>
  <p data-lake-id="u91c1adfa" id="u91c1adfa"><span data-lake-id="u92299916" id="u92299916">我们知道，本地缓存和分布式缓存相比有很多优点，但是最大的缺点就是存在着一致性的问题。于是就衍生出这个问题：如何保证本地缓存的一致性。</span></p>
  <p data-lake-id="ub22bd629" id="ub22bd629"><span data-lake-id="uab063ceb" id="uab063ceb">​</span><br></p>
  <blockquote data-lake-id="u3ea78c87" id="u3ea78c87">
   <p data-lake-id="ub8a0ad68" id="ub8a0ad68"><span data-lake-id="u632681ab" id="u632681ab">在回答这个问题之前，我先多说几句，这个问题挺好的，可以激发大家的解决问题的想法。但是后面我要说的不管哪个方案，如果真的是用在项目中，那么一定是不合理的。</span></p>
   <p data-lake-id="u892374a1" id="u892374a1"><span data-lake-id="u7b1e4c84" id="u7b1e4c84">因为本地缓存就是通过牺牲一致性来提升效率的，如果他能保证一致性，那么也就没分布式缓存什么事儿了。</span></p>
   <p data-lake-id="u21a6ee2e" id="u21a6ee2e"><span data-lake-id="u127a18ae" id="u127a18ae">虽然我们可以用接下来要介绍的手段在一定程度上解决，但是我要强调的是，很多方案你做了之后，本地缓存自身的优势可能也就没了。</span></p>
   <p data-lake-id="ueddbb8c1" id="ueddbb8c1"><span data-lake-id="u47d47a56" id="u47d47a56">所以，如果有一致性要求，那么就不要用本地缓存！！！</span></p>
  </blockquote>
  <p data-lake-id="ua7c720ee" id="ua7c720ee"><span data-lake-id="u68e35616" id="u68e35616">​</span><br></p>
  <p data-lake-id="uf4292012" id="uf4292012"><span data-lake-id="u2f4ede37" id="u2f4ede37">想要让本地缓存保持一致性，那么也就意味着，当一台机器上的本地缓存更新或者失效时，别的机器也要感知到，并且能及时处理。</span></p>
  <p data-lake-id="ue16ef87b" id="ue16ef87b"><br></p>
  <p data-lake-id="ub2367fa1" id="ub2367fa1"><span data-lake-id="uc2e98448" id="uc2e98448">首先， 有一个方案，就是我们给本地缓存记录一个版本，当某台机器的本地缓存更新之后，把他的最新的版本号以及缓存中的数据记录到数据库中，这样，当下次有请求请求到一个未更新过本地缓存的机器时，对比一下版本号，发现版本号旧了，那么就从数据库中更新一下本地缓存。</span></p>
  <p data-lake-id="u75b76894" id="u75b76894"><span data-lake-id="ucf66b394" id="ucf66b394">​</span><br></p>
  <p data-lake-id="ue779ff6d" id="ue779ff6d"><span data-lake-id="u13cdb4a0" id="u13cdb4a0">其次，当某一个机器上的本地缓存发生变更之后，向配置中心做一次配置变更，然后通过配置中心把变更再推送到每一台机器上，大家监听配置变化做本地缓存的更新。</span></p>
  <p data-lake-id="ud1843f8b" id="ud1843f8b"><span data-lake-id="u577c343d" id="u577c343d">​</span><br></p>
  <p data-lake-id="u44cafa3c" id="u44cafa3c"><span data-lake-id="u14d30991" id="u14d30991">除了数据库和配置中心，也可以用redis，但是，这就有点脱裤子放屁了，用了本地缓存，还要去检查redis和数据库。。。</span></p>
  <p data-lake-id="u4815be36" id="u4815be36"><span data-lake-id="u43ad9052" id="u43ad9052">​</span><br></p>
  <p data-lake-id="u23525279" id="u23525279"><span data-lake-id="u008f23b3" id="u008f23b3">其实，</span><u><span data-lake-id="u36345afd" id="u36345afd">真正的工作中，一般来说，对于本地缓存的使用，一般都是这样的</span></u><span data-lake-id="ud1210bfa" id="ud1210bfa">：</span></p>
  <p data-lake-id="u4965ace8" id="u4965ace8"><span data-lake-id="ub185d6c3" id="ub185d6c3">​</span><br></p>
  <p data-lake-id="u4f02d9c8" id="u4f02d9c8"><span data-lake-id="uc1f25473" id="uc1f25473">首先，肯定是要评估数据的变化频率，对于变化不频繁的数据，才会考虑放到本地缓存中。那种频繁更新的数据，其实并不适合放到本地缓存。比如数据库的库存，你见过哪个公司秒杀是在本地缓存做的？本地缓存这么快，咋不用呢？因为他就不适合啊。</span></p>
  <p data-lake-id="ufcfc2710" id="ufcfc2710"><span data-lake-id="u1ad7611c" id="u1ad7611c">​</span><br></p>
  <p data-lake-id="u1bf9cab4" id="u1bf9cab4"><span data-lake-id="u98304a2f" id="u98304a2f">还有就是，要提前评估下业务上能否接收不一致，以及能接受的不一致的时长。如果接受不了不一致，那就绝对不能用本地缓存。</span></p>
  <p data-lake-id="u46a7b1d0" id="u46a7b1d0"><span data-lake-id="u9ba3ac12" id="u9ba3ac12">​</span><br></p>
  <p data-lake-id="ud1059176" id="ud1059176"><span data-lake-id="u47c47a3e" id="u47c47a3e">如果能接受，那么就基于业务上能接受的时长设置失效时长，比如业务上可以接受10分钟的延迟，那么我们可以设置个8分钟的超时时间。这样到期之后这个缓存的内容就会自动失效。</span></p>
  <p data-lake-id="uf854459a" id="uf854459a"><span data-lake-id="u8e7daa72" id="u8e7daa72">​</span><br></p>
  <p data-lake-id="u00f404bb" id="u00f404bb"><span data-lake-id="uad39e614" id="uad39e614">在初始化缓存的时候，可以设置参数，如expireAfterAccess、expireAfterWrite、refreshAfterWrite，利用这些参数我们可以配置自动更新及自动失效。</span></p>
  <p data-lake-id="u9ab14f6f" id="u9ab14f6f"><span data-lake-id="u29eca0d2" id="u29eca0d2">​</span><br></p>
  <p data-lake-id="u5d7cc535" id="u5d7cc535"><strong><span data-lake-id="u58ec7beb" id="u58ec7beb">自动失效</span></strong><span data-lake-id="u89018ca1" id="u89018ca1">：</span></p>
  <pre lang="java"><code>
Cache&lt;String, String&gt; cache = Caffeine.newBuilder()
                .expireAfterWrite(5, TimeUnit.SECONDS) // 设置缓存项写入后的过期时间为5秒
                .build();
</code></pre>
  <p data-lake-id="u423dd6fa" id="u423dd6fa"><br></p>
  <p data-lake-id="u9bee07b3" id="u9bee07b3"><span data-lake-id="u904d9b6b" id="u904d9b6b">在自动失效后，查询本地缓存就会有一次cache miss，然后下次再查询就会去分布式缓存查询，然后再缓存到本地缓存中即可。这样就能保持最新数据了。</span></p>
  <p data-lake-id="ud4a877f3" id="ud4a877f3"><br></p>
  <p data-lake-id="u65c2ebd7" id="u65c2ebd7"><span data-lake-id="u85d70484" id="u85d70484">这是让缓存自动失效的方式，还有一种可以让本地缓存自动更新的方式。如Cffeine就支持可以定义一个refresh策略，他会定时的进行数据的刷新。</span></p>
  <p data-lake-id="u113c79de" id="u113c79de"><span data-lake-id="ua9f4889f" id="ua9f4889f">​</span><br></p>
  <p data-lake-id="ubccf95c6" id="ubccf95c6"><strong><span data-lake-id="u0750d931" id="u0750d931">自动更新</span></strong><span data-lake-id="u9e0f4026" id="u9e0f4026">：</span></p>
  <p data-lake-id="ufc3050cb" id="ufc3050cb"><span data-lake-id="uf0a073fd" id="uf0a073fd">​</span><br></p>
  <pre lang="java"><code>
Cache&lt;String, String&gt; cache = Caffeine.newBuilder()
                .refreshAfterWrite(5, TimeUnit.SECONDS) // 设置缓存项写入后的自动刷新时间为5秒
                .build(new CacheLoader&lt;String, String&gt;() { //定义一个CacheLoader，实现load方法。
                 	@Override
                 	public ListenableFuture&lt;String&gt; reload(String key, String oldValue) throws Exception {
                     return remoteCache.get(key);
                	}
               });
</code></pre>
  <p data-lake-id="u14fe6d7f" id="u14fe6d7f"><br></p>
  <p data-lake-id="u6510d369" id="u6510d369"><span data-lake-id="ud377031e" id="ud377031e">以上，会在达到缓存刷新的时间后，Caffiene会自动调用load方法进行数据读取并更新。</span></p>
  <p data-lake-id="u4c5f59bf" id="u4c5f59bf"><span data-lake-id="u31f4e1cb" id="u31f4e1cb">​</span><br></p>
 </body>
</html>